/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package edu.utx.spot.timesync.protocol;

// import com.sun.spot.peripheral.NoRouteException;
import com.sun.spot.peripheral.TimeoutException;
import com.sun.spot.util.*;
import com.sun.spot.io.j2me.radiogram.*;
import com.sun.squawk.VM;

import java.io.IOException;
import java.util.Random;
import javax.microedition.io.*;

/**
 *
 * @author diego
 */
public class TimeClient {

    private RadiogramConnection syncConnection;
    private RadiogramConnection delayRequestConnection;

    private byte requestID = 0;
    private byte syncLevel = 100;

    private long timeReferenceAddress = 0;
    private final int SYNC_PERIOD_MS = 10000;
    private final int RECEIVE_TIMEOUT = 3000;
    private Random random;

    private IClockController clockController;

    public TimeClient(IClockController clockController) throws IOException {
        this.syncConnection = (RadiogramConnection) Connector.open("radiogram://:40");
        this.delayRequestConnection = null;
        this.random = new Random();
        this.clockController = clockController;
        startClientThread();
    }

    private void startClientThread() {
        Thread serverThread = new Thread() {
            public void run() {
                System.out.println("[client] starting timesync");
                while (true) {
                    try {
                        receiveSyncBroadcast();
                    }
                    catch (TimeoutException e){
                        System.out.println("receive timeout");
                    }
                    catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        VM.setAsDaemonThread(serverThread);
        serverThread.start();
    }

    private void receiveSyncBroadcast() throws IOException {
        final int size = syncConnection.getMaximumLength();
        System.out.println("[client] wait for sync broadcast");
        Radiogram syncMsg = (Radiogram) syncConnection.newDatagram(size);

        syncConnection.setTimeout(-1); // wait forever
        syncConnection.receive(syncMsg);
        syncConnection.setTimeout(RECEIVE_TIMEOUT);

        if (syncMsg.readByte() != 0x1) {
            return;
        }

        System.out.println("[client] sync broadcast from " + syncMsg.getAddress());

        final byte syncID = syncMsg.readByte();
        final byte refSyncLevel = syncMsg.readByte();

        if (refSyncLevel > syncLevel) {
            return;
        }
        
        if (refSyncLevel < syncLevel) {
            System.out.println("[client] found better reference: " + refSyncLevel);
            System.out.println("[client] open connection to: " + syncMsg.getAddress());

            if (delayRequestConnection != null) {
                System.out.println("[client] closing connection to " +
                        IEEEAddress.toDottedHex(timeReferenceAddress));
                delayRequestConnection.close();
            }
            
            delayRequestConnection = (RadiogramConnection)
                    Connector.open("radiogram://" + syncMsg.getAddress() + ":41");
            delayRequestConnection.setTimeout(RECEIVE_TIMEOUT);

            //
            timeReferenceAddress = syncMsg.getAddressAsLong();
            syncLevel = refSyncLevel;
        }

        if (syncMsg.getAddressAsLong() != timeReferenceAddress) {
            return;
        }
        
        final long syncRxTimestamp = syncMsg.getSyncTimestamp();

        syncMsg.reset();
        syncConnection.receive(syncMsg);

        if (syncMsg.readByte() != 0x3) {
            return;
        }

        if (syncMsg.readByte() != syncID) {
            return;
        }

        syncMsg.skipBytes(1); // skip syncLevel

        final long syncTxTimestamp = syncMsg.readLong();

        // TODO. skip delay request randomly
        //  if (random.nextInt(10) < 7)
        //      return;
        ///

        // wait a bit
        Utils.sleep(random.nextInt(2000));

        // get the transmission timestamp and send in a follow up message

        requestID += 1;

        System.out.println("[client] send delay request: " + requestID);
        Radiogram delayRequest = (Radiogram) delayRequestConnection.newDatagram(2);
        delayRequest.writeByte(0x10);
        delayRequest.writeByte(requestID);
        delayRequestConnection.send(delayRequest);
        final long delayTxTimestamp = delayRequest.getSyncTimestamp();

        Radiogram delayReply = (Radiogram) delayRequestConnection.newDatagram(size);
        delayRequestConnection.receive(delayReply);
        
        if (delayReply.readByte() != 0x30) {
            return;
        }
        if (delayReply.readByte() != requestID) {
            return;
        }
        final long delayRxTimestamp = delayReply.readLong();

        System.out.println("[client] sync tx timestamp : " + syncTxTimestamp);
        System.out.println("[client] sync rx timestamp : " + syncRxTimestamp);
        System.out.println("[client] delay tx timestamp: " + delayTxTimestamp);
        System.out.println("[client] delay rx timestamp: " + delayRxTimestamp);

        final long s2cDelay = (syncRxTimestamp - syncTxTimestamp);
        final long c2sDelay = (delayRxTimestamp - delayTxTimestamp);
        final long propagationDelay = (s2cDelay/2 + c2sDelay/2);
        final long timeError = s2cDelay - propagationDelay;
        
        System.out.println("[client] s2cDelay : " + s2cDelay);
        System.out.println("[client] c2sDelay : " + c2sDelay);
        System.out.println("[client] Delay    : " + propagationDelay);
        System.out.println("[client] timeError: " + timeError);

        if (syncRxTimestamp == 0  || syncTxTimestamp == 0 ||
            delayRxTimestamp == 0 || delayTxTimestamp == 0) {
            System.out.println("[client] zero timestamp.  skip controller");
            return;
        }

        clockController.updateServerToClientOffset(syncRxTimestamp - syncTxTimestamp);
        clockController.updateClientToServerOffset(delayRxTimestamp - delayTxTimestamp);
    }
}
